////
////  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
////
//
//import Foundation
//import WebRTC
//import PromiseKit
//import SignalServiceKit
//import SignalMessaging
//
//// TODO: Add category so that button handlers can be defined where button is created.
//// TODO: Ensure buttons enabled & disabled as necessary.
//class CallViewController: OWSViewController, CallObserver, CallServiceObserver, CallAudioServiceDelegate {
//
//    // Dependencies
//
//    var callUIAdapter: CallUIAdapter {
//        return AppEnvironment.shared.callService.callUIAdapter
//    }
//
//    var proximityMonitoringManager: OWSProximityMonitoringManager {
//        return Environment.shared.proximityMonitoringManager
//    }
//
//    // Feature Flag
//    @objc public static let kShowCallViewOnSeparateWindow = true
//
//    let contactsManager: OWSContactsManager
//
//    // MARK: - Properties
//
//    let thread: TSContactThread
//    let call: SignalCall
//    var hasDismissed = false
//
//    // MARK: - Views
//
//    var hasConstraints = false
//    var blurView: UIVisualEffectView!
//    var dateFormatter: DateFormatter?
//
//    // MARK: - Contact Views
//
//    var contactNameLabel: MarqueeLabel!
//    var contactAvatarView: AvatarImageView!
//    var contactAvatarContainerView: UIView!
//    var callStatusLabel: UILabel!
//    var callDurationTimer: Timer?
//    var leaveCallViewButton: UIButton!
//
//    // MARK: - Ongoing Call Controls
//
//    var ongoingCallControls: UIStackView!
//
//    var ongoingAudioCallControls: UIStackView!
//    var ongoingVideoCallControls: UIStackView!
//
//    var hangUpButton: UIButton!
//    var audioSourceButton: UIButton!
//    var audioModeMuteButton: UIButton!
//    var audioModeVideoButton: UIButton!
//    var videoModeMuteButton: UIButton!
//    var videoModeVideoButton: UIButton!
//    var videoModeFlipCameraButton: UIButton!
//
//    // MARK: - Incoming Call Controls
//
//    var incomingCallControls: UIStackView!
//
//    var acceptIncomingButton: UIButton!
//    var declineIncomingButton: UIButton!
//
//    // MARK: - Video Views
//
//    var remoteVideoView: RemoteVideoView!
//    var localVideoView: RTCCameraPreviewView!
//    var hasShownLocalVideo = false
//    weak var localCaptureSession: AVCaptureSession?
//    weak var remoteVideoTrack: RTCVideoTrack?
//
//    override public var canBecomeFirstResponder: Bool {
//        return true
//    }
//
//    var shouldRemoteVideoControlsBeHidden = false {
//        didSet {
//            updateCallUI(callState: call.state)
//        }
//    }
//
//    // MARK: - Settings Nag Views
//
//    var isShowingSettingsNag = false {
//        didSet {
//            if oldValue != isShowingSettingsNag {
//                updateCallUI(callState: call.state)
//            }
//        }
//    }
//    var settingsNagView: UIView!
//    var settingsNagDescriptionLabel: UILabel!
//
//    // MARK: - Audio Source
//
//    var hasAlternateAudioSources: Bool {
//        Logger.info("available audio sources: \(allAudioSources)")
//        // internal mic and speakerphone will be the first two, any more than one indicates e.g. an attached bluetooth device.
//
//        // TODO is this sufficient? Are their devices w/ bluetooth but no external speaker? e.g. ipod?
//        return allAudioSources.count > 2
//    }
//
//    var allAudioSources: Set<AudioSource> = Set()
//
//    var appropriateAudioSources: Set<AudioSource> {
//        if call.hasLocalVideo {
//            let appropriateForVideo = allAudioSources.filter { audioSource in
//                if audioSource.isBuiltInSpeaker {
//                    return true
//                } else {
//                    guard let portDescription = audioSource.portDescription else {
//                        owsFailDebug("Only built in speaker should be lacking a port description.")
//                        return false
//                    }
//
//                    // Don't use receiver when video is enabled. Only bluetooth or speaker
//                    return portDescription.portType != AVAudioSession.Port.builtInMic
//                }
//            }
//            return Set(appropriateForVideo)
//        } else {
//            return allAudioSources
//        }
//    }
//
//    // MARK: - Initializers
//
//    @available(*, unavailable, message: "use init(call:) constructor instead.")
//    required init?(coder aDecoder: NSCoder) {
//        notImplemented()
//    }
//
//    required init(call: SignalCall) {
//        contactsManager = Environment.shared.contactsManager
//        self.call = call
//        self.thread = TSContactThread.getOrCreateThread(contactId: call.remotePhoneNumber)
//        super.init(nibName: nil, bundle: nil)
//
//        allAudioSources = Set(callUIAdapter.audioService.availableInputs)
//
//        self.shouldUseTheme = false
//    }
//
//    deinit {
//        Logger.info("")
//        NotificationCenter.default.removeObserver(self)
//        self.proximityMonitoringManager.remove(lifetime: self)
//    }
//
//    @objc func didBecomeActive() {
//        if (self.isViewLoaded) {
//            shouldRemoteVideoControlsBeHidden = false
//        }
//    }
//
//    // MARK: - View Lifecycle
//
//    override func viewDidDisappear(_ animated: Bool) {
//        super.viewDidDisappear(animated)
//
//        callDurationTimer?.invalidate()
//        callDurationTimer = nil
//    }
//
//    override func viewWillAppear(_ animated: Bool) {
//        super.viewWillAppear(animated)
//
//        ensureProximityMonitoring()
//
//        updateCallUI(callState: call.state)
//
//        self.becomeFirstResponder()
//    }
//
//    override func viewDidAppear(_ animated: Bool) {
//        super.viewDidAppear(animated)
//
//        self.becomeFirstResponder()
//    }
//
//    override func loadView() {
//        self.view = UIView()
//        self.view.backgroundColor = UIColor.black
//        self.view.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)
//
//        createViews()
//        createViewConstraints()
//    }
//
//    override func viewDidLoad() {
//        super.viewDidLoad()
//
//        contactNameLabel.text = contactsManager.stringForConversationTitle(withPhoneIdentifier: thread.contactIdentifier())
//        updateAvatarImage()
//        NotificationCenter.default.addObserver(forName: .OWSContactsManagerSignalAccountsDidChange, object: nil, queue: nil) { [weak self] _ in
//            guard let strongSelf = self else { return }
//            Logger.info("updating avatar image")
//            strongSelf.updateAvatarImage()
//        }
//
//        // Subscribe for future call updates
//        call.addObserverAndSyncState(observer: self)
//
//        AppEnvironment.shared.callService.addObserverAndSyncState(observer: self)
//
//        assert(callUIAdapter.audioService.delegate == nil)
//        callUIAdapter.audioService.delegate = self
//
//        NotificationCenter.default.addObserver(self,
//                                               selector: #selector(didBecomeActive),
//                                               name: NSNotification.Name.OWSApplicationDidBecomeActive,
//                                               object: nil)
//    }
//
//    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
//        return .portrait
//    }
//
//    override var preferredStatusBarStyle: UIStatusBarStyle {
//        return .lightContent
//    }
//
//    // MARK: - Create Views
//
//    func createViews() {
//        self.view.isUserInteractionEnabled = true
//        self.view.addGestureRecognizer(UITapGestureRecognizer(target: self,
//                                                              action: #selector(didTouchRootView)))
//
//        videoHintView.delegate = self
//
//        // Dark blurred background.
//        let blurEffect = UIBlurEffect(style: .dark)
//        blurView = UIVisualEffectView(effect: blurEffect)
//        blurView.isUserInteractionEnabled = false
//        self.view.addSubview(blurView)
//
//        // Create the video views first, as they are under the other views.
//        createVideoViews()
//        createContactViews()
//        createOngoingCallControls()
//        createIncomingCallControls()
//        createSettingsNagViews()
//    }
//
//    @objc func didTouchRootView(sender: UIGestureRecognizer) {
//        if !remoteVideoView.isHidden {
//            shouldRemoteVideoControlsBeHidden = !shouldRemoteVideoControlsBeHidden
//        }
//    }
//
//    func createVideoViews() {
//        remoteVideoView = RemoteVideoView()
//        remoteVideoView.isUserInteractionEnabled = false
//        localVideoView = RTCCameraPreviewView()
//
//        remoteVideoView.isHidden = true
//        localVideoView.isHidden = true
//        self.view.addSubview(remoteVideoView)
//        self.view.addSubview(localVideoView)
//    }
//
//    func createContactViews() {
//
//        leaveCallViewButton = UIButton()
//        let backButtonImage = CurrentAppContext().isRTL ? #imageLiteral(resourceName: "NavBarBackRTL") : #imageLiteral(resourceName: "NavBarBack")
//        leaveCallViewButton.setImage(backButtonImage, for: .normal)
//        leaveCallViewButton.autoSetDimensions(to: CGSize(width: 40, height: 40))
//        leaveCallViewButton.addTarget(self, action: #selector(didTapLeaveCall(sender:)), for: .touchUpInside)
//        self.view.addSubview(leaveCallViewButton)
//
//        contactNameLabel = MarqueeLabel()
//
//        // marquee config
//        contactNameLabel.type = .continuous
//        // This feels pretty slow when you're initially waiting for it, but when you're overlaying video calls, anything faster is distracting.
//        contactNameLabel.speed = .duration(30.0)
//        contactNameLabel.animationCurve = .linear
//        contactNameLabel.fadeLength = 10.0
//        contactNameLabel.animationDelay = 5
//        // Add trailing space after the name scrolls before it wraps around and scrolls back in.
//        contactNameLabel.trailingBuffer = ScaleFromIPhone5(80.0)
//
//        // label config
//        contactNameLabel.font = UIFont.ows_dynamicTypeTitle1
//        contactNameLabel.textAlignment = .center
//        contactNameLabel.textColor = UIColor.white
//        contactNameLabel.layer.shadowOffset = CGSize.zero
//        contactNameLabel.layer.shadowOpacity = 0.35
//        contactNameLabel.layer.shadowRadius = 4
//
//        self.view.addSubview(contactNameLabel)
//
//        callStatusLabel = UILabel()
//        callStatusLabel.font = UIFont.ows_dynamicTypeBody
//        callStatusLabel.textAlignment = .center
//        callStatusLabel.textColor = UIColor.white
//        callStatusLabel.layer.shadowOffset = CGSize.zero
//        callStatusLabel.layer.shadowOpacity = 0.35
//        callStatusLabel.layer.shadowRadius = 4
//
//        self.view.addSubview(callStatusLabel)
//
//        contactAvatarContainerView = UIView.container()
//        self.view.addSubview(contactAvatarContainerView)
//        contactAvatarView = AvatarImageView()
//        contactAvatarContainerView.addSubview(contactAvatarView)
//    }
//
//    func createSettingsNagViews() {
//        settingsNagView = UIView()
//        settingsNagView.isHidden = true
//        self.view.addSubview(settingsNagView)
//
//        let viewStack = UIView()
//        settingsNagView.addSubview(viewStack)
//        viewStack.autoPinWidthToSuperview()
//        viewStack.autoVCenterInSuperview()
//
//        settingsNagDescriptionLabel = UILabel()
//        settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL",
//                                                             comment: "Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy.")
//        settingsNagDescriptionLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(16, 18))
//        settingsNagDescriptionLabel.textColor = UIColor.white
//        settingsNagDescriptionLabel.numberOfLines = 0
//        settingsNagDescriptionLabel.lineBreakMode = .byWordWrapping
//        viewStack.addSubview(settingsNagDescriptionLabel)
//        settingsNagDescriptionLabel.autoPinWidthToSuperview()
//        settingsNagDescriptionLabel.autoPinEdge(toSuperviewEdge: .top)
//
//        let buttonHeight = ScaleFromIPhone5To7Plus(35, 45)
//        let descriptionVSpacingHeight = ScaleFromIPhone5To7Plus(30, 60)
//
//        let callSettingsButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS",
//                                                                              comment: "Label for button that shows the privacy settings."),
//                                                      font: OWSFlatButton.fontForHeight(buttonHeight),
//                                                      titleColor: UIColor.white,
//                                                      backgroundColor: UIColor.ows_signalBrandBlue,
//                                                      target: self,
//                                                      selector: #selector(didPressShowCallSettings))
//        viewStack.addSubview(callSettingsButton)
//        callSettingsButton.autoSetDimension(.height, toSize: buttonHeight)
//        callSettingsButton.autoPinWidthToSuperview()
//        callSettingsButton.autoPinEdge(.top, to: .bottom, of: settingsNagDescriptionLabel, withOffset: descriptionVSpacingHeight)
//
//        let notNowButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_NOT_NOW_BUTTON",
//                                                                        comment: "Label for button that dismiss the call view's settings nag."),
//                                                font: OWSFlatButton.fontForHeight(buttonHeight),
//                                                titleColor: UIColor.white,
//                                                backgroundColor: UIColor.ows_signalBrandBlue,
//                                                target: self,
//                                                selector: #selector(didPressDismissNag))
//        viewStack.addSubview(notNowButton)
//        notNowButton.autoSetDimension(.height, toSize: buttonHeight)
//        notNowButton.autoPinWidthToSuperview()
//        notNowButton.autoPinEdge(toSuperviewEdge: .bottom)
//        notNowButton.autoPinEdge(.top, to: .bottom, of: callSettingsButton, withOffset: 12)
//    }
//
//    func buttonSize() -> CGFloat {
//        return ScaleFromIPhone5To7Plus(84, 108)
//    }
//
//    func buttonInset() -> CGFloat {
//        return ScaleFromIPhone5To7Plus(7, 9)
//    }
//
//    func createOngoingCallControls() {
//
//        audioSourceButton = createButton(image: #imageLiteral(resourceName: "audio-call-speaker-inactive"),
//                                          action: #selector(didPressAudioSource))
//        audioSourceButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_AUDIO_SOURCE_LABEL",
//                                                                 comment: "Accessibility label for selection the audio source")
//
//        hangUpButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"),
//                                    action: #selector(didPressHangup))
//        hangUpButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_HANGUP_LABEL",
//                                                            comment: "Accessibility label for hang up call")
//
//        audioModeMuteButton = createButton(image: #imageLiteral(resourceName: "audio-call-mute-inactive"),
//                                           action: #selector(didPressMute))
//        audioModeMuteButton.setImage(#imageLiteral(resourceName: "audio-call-mute-active"), for: .selected)
//
//        audioModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL",
//                                                                   comment: "Accessibility label for muting the microphone")
//
//        audioModeVideoButton = createButton(image: #imageLiteral(resourceName: "audio-call-video-inactive"),
//                                            action: #selector(didPressVideo))
//        audioModeVideoButton.setImage(#imageLiteral(resourceName: "audio-call-video-active"), for: .selected)
//        audioModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_VIDEO_LABEL", comment: "Accessibility label to switch to video call")
//
//        videoModeMuteButton = createButton(image: #imageLiteral(resourceName: "video-mute-unselected"),
//                                           action: #selector(didPressMute))
//        videoModeMuteButton.setImage(#imageLiteral(resourceName: "video-mute-selected"), for: .selected)
//        videoModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone")
//        videoModeMuteButton.alpha = 0.9
//
//        videoModeFlipCameraButton = createButton(image: #imageLiteral(resourceName: "video-switch-camera-unselected"),
//                                                 action: #selector(didPressFlipCamera))
//
//        videoModeFlipCameraButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_CAMERA_DIRECTION", comment: "Accessibility label to toggle front- vs. rear-facing camera")
//        videoModeFlipCameraButton.alpha = 0.9
//
//        videoModeVideoButton = createButton(image: #imageLiteral(resourceName: "video-video-unselected"),
//                                            action: #selector(didPressVideo))
//        videoModeVideoButton.setImage(#imageLiteral(resourceName: "video-video-selected"), for: .selected)
//        videoModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_AUDIO_LABEL", comment: "Accessibility label to switch to audio only")
//        videoModeVideoButton.alpha = 0.9
//
//        ongoingCallControls = UIStackView(arrangedSubviews: [hangUpButton])
//        ongoingCallControls.axis = .vertical
//        ongoingCallControls.alignment = .center
//        view.addSubview(ongoingCallControls)
//
//        ongoingAudioCallControls = UIStackView(arrangedSubviews: [audioModeMuteButton, audioSourceButton, audioModeVideoButton])
//        ongoingAudioCallControls.distribution = .equalSpacing
//        ongoingAudioCallControls.axis = .horizontal
//
//        ongoingVideoCallControls = UIStackView(arrangedSubviews: [videoModeMuteButton, videoModeFlipCameraButton, videoModeVideoButton])
//        ongoingAudioCallControls.distribution = .equalSpacing
//        ongoingVideoCallControls.axis = .horizontal
//    }
//
//    func presentAudioSourcePicker() {
//        AssertIsOnMainThread()
//
//        let actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
//
//        let dismissAction = UIAlertAction(title: CommonStrings.dismissButton, style: .cancel, handler: nil)
//        actionSheetController.addAction(dismissAction)
//
//        let currentAudioSource = callUIAdapter.audioService.currentAudioSource(call: self.call)
//        for audioSource in self.appropriateAudioSources {
//            let routeAudioAction = UIAlertAction(title: audioSource.localizedName, style: .default) { _ in
//                self.callUIAdapter.setAudioSource(call: self.call, audioSource: audioSource)
//            }
//
//            // HACK: private API to create checkmark for active audio source.
//            routeAudioAction.setValue(currentAudioSource == audioSource, forKey: "checked")
//
//            // TODO: pick some icons. Leaving out for MVP
//            // HACK: private API to add image to actionsheet
//            // routeAudioAction.setValue(audioSource.image, forKey: "image")
//
//            actionSheetController.addAction(routeAudioAction)
//        }
//
//        // Note: It's critical that we present from this view and
//        // not the "frontmost view controller" since this view may
//        // reside on a separate window.
//        presentAlert(actionSheetController)
//    }
//
//    func updateAvatarImage() {
//        contactAvatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: 400)
//    }
//
//    func createIncomingCallControls() {
//
//        acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"),
//                                            action: #selector(didPressAnswerCall))
//        acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL",
//                                                                    comment: "Accessibility label for accepting incoming calls")
//        declineIncomingButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"),
//                                             action: #selector(didPressDeclineCall))
//        declineIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_DECLINE_INCOMING_CALL_LABEL",
//                                                                     comment: "Accessibility label for declining incoming calls")
//
//        incomingCallControls = UIStackView(arrangedSubviews: [acceptIncomingButton, declineIncomingButton])
//        incomingCallControls.axis = .horizontal
//        incomingCallControls.alignment = .center
//        incomingCallControls.distribution = .equalSpacing
//
//        view.addSubview(incomingCallControls)
//    }
//
//    func createButton(image: UIImage, action: Selector) -> UIButton {
//        let button = UIButton()
//        button.setImage(image, for: .normal)
//        button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
//                                              left: buttonInset(),
//                                              bottom: buttonInset(),
//                                              right: buttonInset())
//        button.addTarget(self, action: action, for: .touchUpInside)
//        button.autoSetDimension(.width, toSize: buttonSize())
//        button.autoSetDimension(.height, toSize: buttonSize())
//        return button
//    }
//
//    // MARK: - Layout
//
//    var localVideoViewTopConstraintDefault: NSLayoutConstraint!
//    var localVideoViewTopConstraintHidden: NSLayoutConstraint!
//
//    func createViewConstraints() {
//
//        let contactVSpacing = CGFloat(3)
//        let settingsNagHMargin = CGFloat(30)
//        let ongoingBottomMargin = ScaleFromIPhone5To7Plus(23, 41)
//        let incomingHMargin = ScaleFromIPhone5To7Plus(30, 56)
//        let incomingBottomMargin = CGFloat(41)
//        let settingsNagBottomMargin = CGFloat(41)
//        let avatarTopSpacing = ScaleFromIPhone5To7Plus(25, 50)
//        // The buttons have built-in 10% margins, so to appear centered
//        // the avatar's bottom spacing should be a bit less.
//        let avatarBottomSpacing = ScaleFromIPhone5To7Plus(18, 41)
//        // Layout of the local video view is a bit unusual because
//        // although the view is square, it will be used
//        let videoPreviewHMargin = CGFloat(0)
//
//        // Dark blurred background.
//        blurView.autoPinEdgesToSuperviewEdges()
//
//        leaveCallViewButton.autoPinEdge(toSuperviewEdge: .leading)
//
//        if #available(iOS 11, *) {
//            leaveCallViewButton.autoPinEdge(toSuperviewMargin: .top)
//            contactNameLabel.autoPinEdge(toSuperviewMargin: .top)
//        } else {
//            leaveCallViewButton.autoPin(toTopLayoutGuideOf: self, withInset: 0)
//            contactNameLabel.autoPin(toTopLayoutGuideOf: self, withInset: 0)
//        }
//
//        contactNameLabel.autoPinEdge(.leading, to: .trailing, of: leaveCallViewButton, withOffset: 8, relation: .greaterThanOrEqual)
//        contactNameLabel.autoHCenterInSuperview()
//        contactNameLabel.setContentHuggingVerticalHigh()
//        contactNameLabel.setCompressionResistanceHigh()
//
//        callStatusLabel.autoPinEdge(.top, to: .bottom, of: contactNameLabel, withOffset: contactVSpacing)
//        callStatusLabel.autoHCenterInSuperview()
//        callStatusLabel.setContentHuggingVerticalHigh()
//        callStatusLabel.setCompressionResistanceHigh()
//
//        localVideoView.autoPinTrailingToSuperviewMargin(withInset: videoPreviewHMargin)
//
//        self.localVideoViewTopConstraintDefault = localVideoView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: 4)
//        self.localVideoViewTopConstraintHidden = localVideoView.autoPinEdge(toSuperviewMargin: .top)
//        let localVideoSize = ScaleFromIPhone5To7Plus(80, 100)
//        localVideoView.autoSetDimension(.width, toSize: localVideoSize)
//        localVideoView.autoSetDimension(.height, toSize: localVideoSize)
//
//        remoteVideoView.autoPinEdgesToSuperviewEdges()
//
//        contactAvatarContainerView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: +avatarTopSpacing)
//        contactAvatarContainerView.autoPinEdge(.bottom, to: .top, of: ongoingCallControls, withOffset: -avatarBottomSpacing)
//        contactAvatarContainerView.autoPinWidthToSuperview(withMargin: avatarTopSpacing)
//
//        contactAvatarView.autoCenterInSuperview()
//
//        // Ensure ContacAvatarView gets as close as possible to it's superview edges while maintaining
//        // aspect ratio.
//        contactAvatarView.autoPinToSquareAspectRatio()
//        contactAvatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual)
//        contactAvatarView.autoPinEdge(toSuperviewEdge: .right, withInset: 0, relation: .greaterThanOrEqual)
//        contactAvatarView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual)
//        contactAvatarView.autoPinEdge(toSuperviewEdge: .left, withInset: 0, relation: .greaterThanOrEqual)
//        NSLayoutConstraint.autoSetPriority(UILayoutPriority.defaultLow) {
//            contactAvatarView.autoPinEdgesToSuperviewMargins()
//        }
//
//        // Ongoing call controls
//        ongoingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: ongoingBottomMargin)
//        ongoingCallControls.autoPinLeadingToSuperviewMargin()
//        ongoingCallControls.autoPinTrailingToSuperviewMargin()
//        ongoingCallControls.setContentHuggingVerticalHigh()
//
//        // Incoming call controls
//        incomingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: incomingBottomMargin)
//        incomingCallControls.autoPinLeadingToSuperviewMargin(withInset: incomingHMargin)
//        incomingCallControls.autoPinTrailingToSuperviewMargin(withInset: incomingHMargin)
//        incomingCallControls.setContentHuggingVerticalHigh()
//
//        // Settings nag views
//        settingsNagView.autoPinEdge(toSuperviewEdge: .bottom, withInset: settingsNagBottomMargin)
//        settingsNagView.autoPinWidthToSuperview(withMargin: settingsNagHMargin)
//        settingsNagView.autoPinEdge(.top, to: .bottom, of: callStatusLabel)
//    }
//
//    override func updateViewConstraints() {
//        updateRemoteVideoLayout()
//        updateLocalVideoLayout()
//
//        super.updateViewConstraints()
//    }
//
//    internal func updateRemoteVideoLayout() {
//        remoteVideoView.isHidden = !self.hasRemoteVideoTrack
//        updateCallUI(callState: call.state)
//    }
//
//    let videoHintView = CallVideoHintView()
//
//    internal func updateLocalVideoLayout() {
//        if !localVideoView.isHidden {
//            localVideoView.superview?.bringSubviewToFront(localVideoView)
//        }
//
//        updateCallUI(callState: call.state)
//    }
//
//    // MARK: - Methods
//
//    func showCallFailed(error: Error) {
//        // TODO Show something in UI.
//        Logger.error("call failed with error: \(error)")
//    }
//
//    // MARK: - View State
//
//    func localizedTextForCallState(_ callState: CallState) -> String {
//        assert(Thread.isMainThread)
//
//        switch callState {
//        case .idle, .remoteHangup, .localHangup:
//            return NSLocalizedString("IN_CALL_TERMINATED", comment: "Call setup status label")
//        case .dialing:
//            return NSLocalizedString("IN_CALL_CONNECTING", comment: "Call setup status label")
//        case .remoteRinging, .localRinging:
//            return NSLocalizedString("IN_CALL_RINGING", comment: "Call setup status label")
//        case .answering:
//            return NSLocalizedString("IN_CALL_SECURING", comment: "Call setup status label")
//        case .connected:
//            let callDuration = call.connectionDuration()
//            let callDurationDate = Date(timeIntervalSinceReferenceDate: callDuration)
//            if dateFormatter == nil {
//                dateFormatter = DateFormatter()
//                dateFormatter!.dateFormat = "HH:mm:ss"
//                dateFormatter!.timeZone = TimeZone(identifier: "UTC")!
//            }
//            var formattedDate = dateFormatter!.string(from: callDurationDate)
//            if formattedDate.hasPrefix("00:") {
//                // Don't show the "hours" portion of the date format unless the
//                // call duration is at least 1 hour.
//                formattedDate = String(formattedDate[formattedDate.index(formattedDate.startIndex, offsetBy: 3)...])
//            } else {
//                // If showing the "hours" portion of the date format, strip any leading
//                // zeroes.
//                if formattedDate.hasPrefix("0") {
//                    formattedDate = String(formattedDate[formattedDate.index(formattedDate.startIndex, offsetBy: 1)...])
//                }
//            }
//            return formattedDate
//        case .reconnecting:
//            return NSLocalizedString("IN_CALL_RECONNECTING", comment: "Call setup status label")
//        case .remoteBusy:
//            return NSLocalizedString("END_CALL_RESPONDER_IS_BUSY", comment: "Call setup status label")
//        case .localFailure:
//            if let error = call.error {
//                switch error {
//                case .timeout(description: _):
//                    if self.call.direction == .outgoing {
//                        return NSLocalizedString("CALL_SCREEN_STATUS_NO_ANSWER", comment: "Call setup status label after outgoing call times out")
//                    }
//                default:
//                    break
//                }
//            }
//
//            return NSLocalizedString("END_CALL_UNCATEGORIZED_FAILURE", comment: "Call setup status label")
//        }
//    }
//
//    var isBlinkingReconnectLabel = false
//    func updateCallStatusLabel(callState: CallState) {
//        assert(Thread.isMainThread)
//
//        let text = String(format: CallStrings.callStatusFormat,
//                          localizedTextForCallState(callState))
//        self.callStatusLabel.text = text
//
//        // Handle reconnecting blinking
//        if case .reconnecting = callState {
//            if !isBlinkingReconnectLabel {
//                isBlinkingReconnectLabel = true
//                UIView.animate(withDuration: 0.7, delay: 0, options: [.autoreverse, .repeat],
//                               animations: {
//                                self.callStatusLabel.alpha = 0.2
//                }, completion: nil)
//            } else {
//                // already blinking
//            }
//        } else {
//            // We're no longer in a reconnecting state, either the call failed or we reconnected.
//            // Stop the blinking animation
//            if isBlinkingReconnectLabel {
//                self.callStatusLabel.layer.removeAllAnimations()
//                self.callStatusLabel.alpha = 1
//                isBlinkingReconnectLabel = false
//            }
//        }
//    }
//
//    func updateCallUI(callState: CallState) {
//        assert(Thread.isMainThread)
//        updateCallStatusLabel(callState: callState)
//        if isShowingSettingsNag {
//            settingsNagView.isHidden = false
//            contactAvatarView.isHidden = true
//            ongoingCallControls.isHidden = true
//            return
//        }
//
//        // Marquee scrolling is distracting during a video call, disable it.
//        contactNameLabel.labelize = call.hasLocalVideo
//
//        audioModeMuteButton.isSelected = call.isMuted
//        videoModeMuteButton.isSelected = call.isMuted
//        audioModeVideoButton.isSelected = call.hasLocalVideo
//        videoModeVideoButton.isSelected = call.hasLocalVideo
//
//        // Show Incoming vs. Ongoing call controls
//        let isRinging = callState == .localRinging
//        incomingCallControls.isHidden = !isRinging
//        incomingCallControls.isUserInteractionEnabled = isRinging
//        ongoingCallControls.isHidden = isRinging
//        ongoingCallControls.isUserInteractionEnabled = !isRinging
//
//        // Rework control state if remote video is available.
//        let hasRemoteVideo = !remoteVideoView.isHidden
//        contactAvatarView.isHidden = hasRemoteVideo
//
//        // Rework control state if local video is available.
//        let hasLocalVideo = !localVideoView.isHidden
//
//        if hasLocalVideo {
//            ongoingAudioCallControls.removeFromSuperview()
//            ongoingCallControls.insertArrangedSubview(ongoingVideoCallControls, at: 0)
//        } else {
//            ongoingVideoCallControls.removeFromSuperview()
//            ongoingCallControls.insertArrangedSubview(ongoingAudioCallControls, at: 0)
//        }
//        // Layout immediately to avoid spurious animation.
//        ongoingCallControls.layoutIfNeeded()
//
//        // Also hide other controls if user has tapped to hide them.
//        if shouldRemoteVideoControlsBeHidden && !remoteVideoView.isHidden {
//            leaveCallViewButton.isHidden = true
//            contactNameLabel.isHidden = true
//            callStatusLabel.isHidden = true
//            ongoingCallControls.isHidden = true
//            videoHintView.isHidden = true
//        } else {
//            leaveCallViewButton.isHidden = false
//            contactNameLabel.isHidden = false
//            callStatusLabel.isHidden = false
//
//            if hasRemoteVideo && !hasLocalVideo && !hasShownLocalVideo && !hasUserDismissedVideoHint {
//                view.addSubview(videoHintView)
//                videoHintView.isHidden = false
//                videoHintView.autoPinEdge(.bottom, to: .top, of: audioModeVideoButton)
//                videoHintView.autoPinEdge(.trailing, to: .leading, of: audioModeVideoButton, withOffset: buttonSize() / 2 + videoHintView.kTailHMargin + videoHintView.kTailWidth / 2)
//            } else {
//                videoHintView.removeFromSuperview()
//            }
//        }
//
//        let doLocalVideoLayout = {
//            self.localVideoViewTopConstraintDefault.isActive = !self.contactNameLabel.isHidden
//            self.localVideoViewTopConstraintHidden.isActive = self.contactNameLabel.isHidden
//            self.localVideoView.superview?.layoutIfNeeded()
//        }
//        if hasShownLocalVideo {
//            // Animate.
//            UIView.animate(withDuration: 0.25, animations: doLocalVideoLayout)
//        } else {
//            // Don't animate.
//            doLocalVideoLayout()
//        }
//
//        // Audio Source Handling (bluetooth)
//        if self.hasAlternateAudioSources {
//            // With bluetooth, button does not stay selected. Pressing it pops an actionsheet
//            // and the button should immediately "unselect".
//            audioSourceButton.isSelected = false
//
//            if hasLocalVideo {
//                audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .normal)
//                audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_video_mode"), for: .selected)
//            } else {
//                audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .normal)
//                audioSourceButton.setImage(#imageLiteral(resourceName: "ic_speaker_bluetooth_inactive_audio_mode"), for: .selected)
//            }
//            audioSourceButton.isHidden = false
//        } else {
//            // No bluetooth audio detected
//            audioSourceButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-inactive"), for: .normal)
//            audioSourceButton.setImage(#imageLiteral(resourceName: "audio-call-speaker-active"), for: .selected)
//
//            // If there's no bluetooth, we always use speakerphone, so no need for
//            // a button, giving more screen back for the video.
//            audioSourceButton.isHidden = hasLocalVideo
//        }
//
//        // Dismiss Handling
//        switch callState {
//        case .remoteHangup, .remoteBusy, .localFailure:
//            Logger.debug("dismissing after delay because new state is \(callState)")
//            dismissIfPossible(shouldDelay: true)
//        case .localHangup:
//            Logger.debug("dismissing immediately from local hangup")
//            dismissIfPossible(shouldDelay: false)
//        default: break
//        }
//
//        if callState == .connected {
//            if callDurationTimer == nil {
//                let kDurationUpdateFrequencySeconds = 1 / 20.0
//                callDurationTimer = WeakTimer.scheduledTimer(timeInterval: TimeInterval(kDurationUpdateFrequencySeconds),
//                                                         target: self,
//                                                         userInfo: nil,
//                                                         repeats: true) {[weak self] _ in
//                                                            self?.updateCallDuration()
//                }
//            }
//        } else {
//            callDurationTimer?.invalidate()
//            callDurationTimer = nil
//        }
//    }
//
//    func updateCallDuration() {
//        updateCallStatusLabel(callState: call.state)
//    }
//
//    // We update the audioSourceButton outside of the main `updateCallUI`
//    // because `updateCallUI` is intended to be idempotent, which isn't possible
//    // with external speaker state because:
//    // - the system API which enables the external speaker is a (somewhat slow) asyncronous
//    //   operation
//    // - we want to give immediate UI feedback by marking the pressed button as selected
//    //   before the operation completes.
//    func updateAudioSourceButtonIsSelected() {
//        guard callUIAdapter.audioService.isSpeakerphoneEnabled else {
//            self.audioSourceButton.isSelected = false
//            return
//        }
//
//        // VideoChat mode enables the output speaker, but we don't
//        // want to highlight the speaker button in that case.
//        guard !call.hasLocalVideo else {
//            self.audioSourceButton.isSelected = false
//            return
//        }
//
//        self.audioSourceButton.isSelected = true
//    }
//
//    // MARK: - Actions
//
//    /**
//     * Ends a connected call. Do not confuse with `didPressDeclineCall`.
//     */
//    @objc func didPressHangup(sender: UIButton) {
//        Logger.info("")
//
//        callUIAdapter.localHangupCall(call)
//
//        dismissIfPossible(shouldDelay: false)
//    }
//
//    @objc func didPressMute(sender muteButton: UIButton) {
//        Logger.info("")
//        muteButton.isSelected = !muteButton.isSelected
//
//        callUIAdapter.setIsMuted(call: call, isMuted: muteButton.isSelected)
//    }
//
//    @objc func didPressAudioSource(sender button: UIButton) {
//        Logger.info("")
//
//        if self.hasAlternateAudioSources {
//            presentAudioSourcePicker()
//        } else {
//            didPressSpeakerphone(sender: button)
//        }
//    }
//
//    func didPressSpeakerphone(sender button: UIButton) {
//        Logger.info("")
//
//        button.isSelected = !button.isSelected
//        callUIAdapter.audioService.requestSpeakerphone(isEnabled: button.isSelected)
//    }
//
//    func didPressTextMessage(sender button: UIButton) {
//        Logger.info("")
//
//        dismissIfPossible(shouldDelay: false)
//    }
//
//    @objc func didPressAnswerCall(sender: UIButton) {
//        Logger.info("")
//
//        callUIAdapter.answerCall(call)
//    }
//
//    @objc func didPressVideo(sender: UIButton) {
//        Logger.info("")
//        let hasLocalVideo = !sender.isSelected
//
//        callUIAdapter.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo)
//    }
//
//    @objc func didPressFlipCamera(sender: UIButton) {
//        sender.isSelected = !sender.isSelected
//
//        let isUsingFrontCamera = !sender.isSelected
//        Logger.info("with isUsingFrontCamera: \(isUsingFrontCamera)")
//
//        callUIAdapter.setCameraSource(call: call, isUsingFrontCamera: isUsingFrontCamera)
//    }
//
//    /**
//     * Denies an incoming not-yet-connected call, Do not confuse with `didPressHangup`.
//     */
//    @objc func didPressDeclineCall(sender: UIButton) {
//        Logger.info("")
//
//        callUIAdapter.declineCall(call)
//
//        dismissIfPossible(shouldDelay: false)
//    }
//
//    @objc func didPressShowCallSettings(sender: UIButton) {
//        Logger.info("")
//
//        markSettingsNagAsComplete()
//
//        dismissIfPossible(shouldDelay: false, ignoreNag: true, completion: {
//            // Find the frontmost presented UIViewController from which to present the
//            // settings views.
//            let fromViewController = UIApplication.shared.findFrontmostViewController(ignoringAlerts: true)
//            assert(fromViewController != nil)
//
//            // Construct the "settings" view & push the "privacy settings" view.
//            let navigationController = AppSettingsViewController.inModalNavigationController()
//            navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)
//
//            fromViewController?.present(navigationController, animated: true, completion: nil)
//        })
//    }
//
//    @objc func didPressDismissNag(sender: UIButton) {
//        Logger.info("")
//
//        markSettingsNagAsComplete()
//
//        dismissIfPossible(shouldDelay: false, ignoreNag: true)
//    }
//
//    // We only show the "blocking" settings nag until the user has chosen
//    // to view the privacy settings _or_ dismissed the nag at least once.
//    // 
//    // In either case, we set the "CallKit enabled" and "CallKit privacy enabled" 
//    // settings to their default values to indicate that the user has reviewed
//    // them.
//    private func markSettingsNagAsComplete() {
//        Logger.info("")
//
//        let preferences = Environment.shared.preferences!
//
//        preferences.setIsCallKitEnabled(preferences.isCallKitEnabled())
//        preferences.setIsCallKitPrivacyEnabled(preferences.isCallKitPrivacyEnabled())
//    }
//
//    @objc func didTapLeaveCall(sender: UIButton) {
//        OWSWindowManager.shared().leaveCallView()
//    }
//
//    // MARK: - CallObserver
//
//    internal func stateDidChange(call: SignalCall, state: CallState) {
//        AssertIsOnMainThread()
//        Logger.info("new call status: \(state)")
//
//        self.updateCallUI(callState: state)
//    }
//
//    internal func hasLocalVideoDidChange(call: SignalCall, hasLocalVideo: Bool) {
//        AssertIsOnMainThread()
//        self.updateCallUI(callState: call.state)
//    }
//
//    internal func muteDidChange(call: SignalCall, isMuted: Bool) {
//        AssertIsOnMainThread()
//        self.updateCallUI(callState: call.state)
//    }
//
//    func holdDidChange(call: SignalCall, isOnHold: Bool) {
//        AssertIsOnMainThread()
//        self.updateCallUI(callState: call.state)
//    }
//
//    internal func audioSourceDidChange(call: SignalCall, audioSource: AudioSource?) {
//        AssertIsOnMainThread()
//        self.updateCallUI(callState: call.state)
//    }
//
//    // MARK: - CallAudioServiceDelegate
//
//    func callAudioService(_ callAudioService: CallAudioService, didUpdateIsSpeakerphoneEnabled isSpeakerphoneEnabled: Bool) {
//        AssertIsOnMainThread()
//
//        updateAudioSourceButtonIsSelected()
//    }
//
//    func callAudioServiceDidChangeAudioSession(_ callAudioService: CallAudioService) {
//        AssertIsOnMainThread()
//
//        // Which sources are available depends on the state of your Session.
//        // When the audio session is not yet in PlayAndRecord none are available
//        // Then if we're in speakerphone, bluetooth isn't available.
//        // So we accrue all possible audio sources in a set, and that list lives as longs as the CallViewController
//        // The downside of this is that if you e.g. unpair your bluetooth mid call, it will still appear as an option
//        // until your next call.
//        // FIXME: There's got to be a better way, but this is where I landed after a bit of work, and seems to work
//        // pretty well in practice.
//        let availableInputs = callAudioService.availableInputs
//        self.allAudioSources.formUnion(availableInputs)
//    }
//
//    // MARK: - Video
//
//    internal func updateLocalVideo(captureSession: AVCaptureSession?) {
//
//        AssertIsOnMainThread()
//
//        guard localVideoView.captureSession != captureSession else {
//            Logger.debug("ignoring redundant update")
//            return
//        }
//
//        localVideoView.captureSession = captureSession
//        let isHidden = captureSession == nil
//
//        Logger.info("isHidden: \(isHidden)")
//        localVideoView.isHidden = isHidden
//
//        updateLocalVideoLayout()
//        updateAudioSourceButtonIsSelected()
//
//        // Don't animate layout of local video view until it has been presented.
//        if !isHidden {
//            hasShownLocalVideo = true
//        }
//    }
//
//    var hasRemoteVideoTrack: Bool {
//        return self.remoteVideoTrack != nil
//    }
//
//    var hasUserDismissedVideoHint: Bool = false
//
//    internal func updateRemoteVideoTrack(remoteVideoTrack: RTCVideoTrack?) {
//        AssertIsOnMainThread()
//
//        guard self.remoteVideoTrack != remoteVideoTrack else {
//            Logger.debug("ignoring redundant update")
//            return
//        }
//
//        self.remoteVideoTrack?.remove(remoteVideoView)
//        self.remoteVideoTrack = nil
//        remoteVideoView.renderFrame(nil)
//        self.remoteVideoTrack = remoteVideoTrack
//        self.remoteVideoTrack?.add(remoteVideoView)
//
//        shouldRemoteVideoControlsBeHidden = false
//
//        if remoteVideoTrack != nil {
//            playRemoteEnabledVideoHapticFeedback()
//        }
//
//        updateRemoteVideoLayout()
//    }
//
//    // MARK: Video Haptics
//
//    let feedbackGenerator = NotificationHapticFeedback()
//    var lastHapticTime: TimeInterval = CACurrentMediaTime()
//    func playRemoteEnabledVideoHapticFeedback() {
//        let currentTime = CACurrentMediaTime()
//        guard currentTime - lastHapticTime > 5 else {
//            Logger.debug("ignoring haptic feedback since it's too soon")
//            return
//        }
//        feedbackGenerator.notificationOccurred(.success)
//        lastHapticTime = currentTime
//    }
//
//    // MARK: - Dismiss
//
//    internal func dismissIfPossible(shouldDelay: Bool, ignoreNag ignoreNagParam: Bool = false, completion: (() -> Void)? = nil) {
//        callUIAdapter.audioService.delegate = nil
//
//        let ignoreNag: Bool = {
//            // Nothing to nag about on iOS11
//            if #available(iOS 11, *) {
//                return true
//            } else {
//                // otherwise on iOS10, nag as specified
//                return ignoreNagParam
//            }
//        }()
//
//        if hasDismissed {
//            // Don't dismiss twice.
//            return
//        } else if !ignoreNag &&
//            call.direction == .incoming &&
//            UIDevice.current.supportsCallKit &&
//            (!Environment.shared.preferences.isCallKitEnabled() ||
//                Environment.shared.preferences.isCallKitPrivacyEnabled()) {
//
//            isShowingSettingsNag = true
//
//            // Update the nag view's copy to reflect the settings state.
//            if Environment.shared.preferences.isCallKitEnabled() {
//                settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_PRIVACY",
//                                                                     comment: "Reminder to the user of the benefits of disabling CallKit privacy.")
//            } else {
//                settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL",
//                                                                     comment: "Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy.")
//            }
//            settingsNagDescriptionLabel.superview?.setNeedsLayout()
//
//            if Environment.shared.preferences.isCallKitEnabledSet() ||
//                Environment.shared.preferences.isCallKitPrivacySet() {
//                // User has already touched these preferences, only show
//                // the "fleeting" nag, not the "blocking" nag.
//
//                // Show nag for N seconds.
//                DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
//                    guard let strongSelf = self else { return }
//                    strongSelf.dismissIfPossible(shouldDelay: false, ignoreNag: true)
//                }
//            }
//        } else if shouldDelay {
//            hasDismissed = true
//            DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
//                guard let strongSelf = self else { return }
//                strongSelf.dismissImmediately(completion: completion)
//            }
//        } else {
//            hasDismissed = true
//            dismissImmediately(completion: completion)
//        }
//    }
//
//    internal func dismissImmediately(completion: (() -> Void)?) {
//        if CallViewController.kShowCallViewOnSeparateWindow {
//            OWSWindowManager.shared().endCall(self)
//            completion?()
//        } else {
//            self.dismiss(animated: true, completion: completion)
//        }
//    }
//
//    // MARK: - CallServiceObserver
//
//    internal func didUpdateCall(call: SignalCall?) {
//        // Do nothing.
//    }
//
//    internal func didUpdateVideoTracks(call: SignalCall?,
//                                       localCaptureSession: AVCaptureSession?,
//                                       remoteVideoTrack: RTCVideoTrack?) {
//        AssertIsOnMainThread()
//
//        updateLocalVideo(captureSession: localCaptureSession)
//        updateRemoteVideoTrack(remoteVideoTrack: remoteVideoTrack)
//    }
//
//    // MARK: - Proximity Monitoring
//
//    func ensureProximityMonitoring() {
//        if #available(iOS 11, *) {
//            // BUG: Adding `self` as a Weak reference to the proximityMonitoringManager results in
//            // the CallViewController never being deallocated, which, besides being a memory leak
//            // can interfere with subsequent video capture - presumably because the old capture
//            // session is still retained via the callViewController.localVideoView.
//            //
//            // A code audit has not revealed a retain cycle.
//            //
//            // Using the XCode memory debugger shows that a strong reference is held by
//            // windowManager.callNavigationController->_childViewControllers.
//            // Even though, when inspecting via the debugger, the CallViewController is not shown as
//            // a childViewController.
//            //
//            //     (lldb) po [[[OWSWindowManager sharedManager] callNavigationController] childViewControllers]
//            //     <__NSSingleObjectArrayI 0x1c0418bd0>(
//            //       <OWSWindowRootViewController: 0x13de37550>
//            //     )
//            //
//            // Weirder still, when presenting another CallViewController, the old one remains unallocated
//            // and inspecting it in the memory debugger shows _no_ strong references to it (yet it
//            // is not deallocated). Some weak references do remain - from the proximityMonitoringManager
//            // and the callObserver, both of which use the Weak<T> struct, which could be related.
//            //
//            // In any case, we can apparently avoid this behavior by not adding self as a Weak lifetime
//            // and as of iOS11, the system automatically managages proximityMonitoring
//            // via CallKit and AudioSessions. Proximity monitoring will be enabled whenever a call
//            // is active, unless we switch to VideoChat audio mode (which is actually desirable
//            // behavior), so the proximityMonitoringManager is redundant for calls on iOS11+.
//        } else {
//            // before iOS11, manually enable proximityMonitoring while we're on a call.
//            self.proximityMonitoringManager.add(lifetime: self)
//        }
//    }
//}
//
//extension CallViewController: CallVideoHintViewDelegate {
//    func didTapCallVideoHintView(_ videoHintView: CallVideoHintView) {
//        self.hasUserDismissedVideoHint = true
//        updateRemoteVideoLayout()
//    }
//}
